package com.atguigu.redislock.mylock;

import cn.hutool.core.util.IdUtil;
import lombok.SneakyThrows;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @auther zzyy
 * @create 2022-10-23 22:36
 */
public class RedisDistributedLock implements Lock
{
    private StringRedisTemplate stringRedisTemplate;

    private String lockName;//KEYS[1]
    private String uuidValue;//ARGV[1]
    private long   expireTime;//ARGV[2]

    /*public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName)
    {
        System.out.println("new的构造次数: "+UUID.randomUUID().toString());
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();
        this.expireTime = 30L;
    }*/

    /*static String XUUID = null;
    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName)
    {
        System.out.println("new的构造次数: "+UUID.randomUUID().toString());
        String tempUUID = "";
        if(XUUID == null)
        {
            XUUID = IdUtil.simpleUUID();
            tempUUID = XUUID;
        }else{
            tempUUID = XUUID;
        }
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;

        this.uuidValue = tempUUID+":"+Thread.currentThread().getId();
        this.expireTime = 30L;
    }*/

    /*private static final ThreadLocal<String> THREAD_LOCAL_UUID = ThreadLocal.withInitial(() -> null);
    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName)
    {
        System.out.println("new的构造次数: "+UUID.randomUUID().toString());
        String tempUUID = "";

        *//*if(THREAD_LOCAL_UUID.get() == null) {
            THREAD_LOCAL_UUID.set(IdUtil.simpleUUID());
            tempUUID = THREAD_LOCAL_UUID.get();
        }else{
            tempUUID = THREAD_LOCAL_UUID.get();
        }*//*
        tempUUID = Optional.ofNullable(THREAD_LOCAL_UUID.get()).orElseGet(() -> {
                    THREAD_LOCAL_UUID.set(IdUtil.simpleUUID());
            return THREAD_LOCAL_UUID.get();});

        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;

        this.uuidValue = tempUUID+":"+Thread.currentThread().getId();
        this.expireTime = 30L;
    }*/
    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName,String uuidValue)
    {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuidValue+":"+Thread.currentThread().getId();
        this.expireTime = 30L;
    }

    @Override
    public void lock()
    {
        tryLock();
    }
    @Override
    public boolean tryLock()
    {
        try
        {
            return tryLock(-1L,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    {
        if(time != -1L)
        {
            expireTime = unit.toSeconds(time);
        }
        //private String lockName;//KEYS[1]
        //private String uuidValue;//ARGV[1]
        //private long   expireTime;//ARGV[2]

        String script =
                "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +
                        "redis.call('hincrby',KEYS[1],ARGV[1],1) " +
                        "redis.call('expire',KEYS[1],ARGV[2]) " +
                        "return 1 " +
                        "else " +
                        "return 0 " +
                        "end";
        System.out.println("加锁lockName: "+lockName+"\t"+"uuidValue: "+uuidValue);
        while(!stringRedisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime)))
        {
            //暂停毫秒
            try { TimeUnit.MILLISECONDS.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); }
        }
        this.renewExpire();
        return true;
    }

    @Override
    public void unlock()
    {
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then " +
                        "return nil " +
                        "elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then " +
                        "return redis.call('del',KEYS[1]) " +
                        "else " +
                        "return 0 " +
                        "end";
        System.out.println("解锁lockName: "+lockName+"\t"+"uuidValue: "+uuidValue);
        Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));
        if(flag == null)
        {
            throw new RuntimeException("this lock doesn't  exist....o(╥﹏╥)o");
        }

    }
    private void renewExpire()
    {
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +
                        "return redis.call('expire',KEYS[1],ARGV[2]) " +
                        "else " +
                        "return 0 " +
                        "end";

        new Timer().schedule(new TimerTask()
        {
            @Override
            public void run()
            {
                if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {
                    renewExpire();
                }
            }
        },(this.expireTime * 1000)/3);
    }


    //============================
    //============================
    //============================
    @Override
    public void lockInterruptibly() throws InterruptedException
    {

    }
    @Override
    public Condition newCondition()
    {
        return null;
    }
}


